package org.epic.perleditor.editors;

import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.eclipse.core.resources.IMarker;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.jface.text.IDocument;
import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.IFileEditorInput;
import org.eclipse.ui.texteditor.IDocumentProvider;
import org.epic.core.parser.PerlToken;
import org.epic.core.parser.PerlTokenTypes;
import org.epic.perleditor.PerlEditorPlugin;
import org.epic.perleditor.editors.util.MarkerUtil;
import org.epic.perleditor.preferences.ITaskTagConstants;
import org.epic.perleditor.preferences.TaskTagPreferences;

/**
 * Scans an edited Perl source file for task markers,
 * adding/removing corresponding IMarkers to/from the resource
 * representing the source file.
 * 
 * @author jploski
 */
public class TasksReconciler
{
    private static final String EPIC_AUTOGENERATED = "epic.autogenerated";
    
    private final PerlEditor editor;
    private Pattern[] TODO_PATTERNS;
    private boolean allowWhiteSpace;
    private MarkerUtil markerUtil;
    
    public TasksReconciler(PerlEditor editor)
    {
        this.editor = editor;

    }
    
    public void reconcile()
    {
        IEditorInput input = editor.getEditorInput();
        if (!(input instanceof IFileEditorInput)) return;
        
        IDocumentProvider docProvider = editor.getDocumentProvider();
        if (docProvider == null) return;
        
        IDocument doc = docProvider.getDocument(input);
        if (doc == null) return;
        
        PerlPartitioner partitioner = (PerlPartitioner) PartitionTypes.getPerlPartitioner(doc);
        if (partitioner == null) return;
        
        markerUtil = new MarkerUtil(((IFileEditorInput) input).getFile());         
        markerUtil.clearAllUsedFlags(IMarker.TASK, EPIC_AUTOGENERATED);
        
        IPreferenceStore store = PerlEditorPlugin.getDefault().getPreferenceStore();        
        allowWhiteSpace = store.getBoolean(ITaskTagConstants.ID_WHITESPACE);
        initSearchPatterns(store);
        
        synchronized (partitioner.getTokensLock())
        {
            List<PerlToken> tokens = partitioner.getTokens();
            
            for (Iterator<PerlToken> i = tokens.iterator(); i.hasNext();)
            {
                PerlToken t = i.next();
                if (t.getType() == PerlTokenTypes.COMMENT) parseComment(t);
            }            
        }

        markerUtil.removeUnusedMarkers(IMarker.TASK, EPIC_AUTOGENERATED);
    }
    
    private void addTaskMarker(int start, int stop, int lineNumber, String text)
    {
        Map<String, Object> attributes = new HashMap<String, Object>(11);
        
        attributes.put(IMarker.CHAR_START, new Integer(start));
        attributes.put(IMarker.CHAR_END, new Integer(stop));
        attributes.put(IMarker.LINE_NUMBER, new Integer(lineNumber));
        attributes.put(IMarker.MESSAGE, text);
        attributes.put(EPIC_AUTOGENERATED, new Integer(42));
        markerUtil.addMarker(attributes, IMarker.TASK);
    }
    
    private void initSearchPatterns(IPreferenceStore store)    
    {
        int flags =
            store.getBoolean(ITaskTagConstants.ID_IGNORE_CASE)
            ? Pattern.CASE_INSENSITIVE
            : 0;
        String taskTagList = store.getString(ITaskTagConstants.ID_TASK_TAGS);
        String[] TODO_STRINGS = TaskTagPreferences.parseStringList(taskTagList);
        TODO_PATTERNS = new Pattern[TODO_STRINGS.length];
        
        for (int i = 0; i < TODO_STRINGS.length; i++)
        {
            // construct the search string
            StringBuilder buffy = new StringBuilder();
            buffy.append("#");
            if (allowWhiteSpace) buffy.append("\\s*");
            buffy.append("\\Q");
            buffy.append(TODO_STRINGS[i]);
            buffy.append("\\E");
            TODO_PATTERNS[i] = Pattern.compile(buffy.toString(), flags);
        }   
    }
    
    private void parseComment(PerlToken t)
    {
        for (int i = 0; i < TODO_PATTERNS.length; i++)
            parseComment(t, TODO_PATTERNS[i]);
    }
    
    private void parseComment(PerlToken t, Pattern todoPattern)
    {
        Matcher m = todoPattern.matcher(t.getText());
        if (!m.find()) return;
        
        String todoText = t.getText().substring(m.end()).trim();
        if (todoText.length() > 0)
        {
            if(!markerUtil.isMarkerPresent(
                IMarker.TASK,
                t.getLine(),
                todoText,
                EPIC_AUTOGENERATED,
                true))
            {
                addTaskMarker(
                    t.getOffset() + m.start(),
                    t.getOffset() + m.start() + m.group().length() + todoText.length(),
                    t.getLine(),
                    todoText);
            }
        }
    }
}
